/*
 * 版权所有 (c) 2022-2099 TBIT。保留所有权利。
 */

package com.tbit.main.constant;

import cn.hutool.core.util.StrUtil;
import com.tbit.main.exception.BaseException;
import com.tbit.main.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * 拦截声明了 {@link DistributedLock} 注解的方法，实现分布式锁操作
 *
 * @author <a href="mailto:li.yao@tbit.com.cn">yao</a>
 * @version 4.0.0
 * @since 2024/5/11 10:18
 */
@Aspect
@Slf4j
@Component
public class DistributedLockAspect {

    @Resource
    private RedisService redisService;

    @Around(value = "@annotation(distributedLock)")
    public Object aroundPointCut(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
        // 1. 锁定 Key
        String lockKey = getLockKey(joinPoint, distributedLock);
        boolean success = redisService.setNx(lockKey, StrUtil.EMPTY, distributedLock.timeout());
        // 锁定失败，抛出异常
        if (!success) {
            throw new BaseException(distributedLock.message());
        }
        // 2. 执行逻辑
        try {
            return joinPoint.proceed();
        } finally {
            // 施法锁
            redisService.del(lockKey);
        }
    }

    private String getLockKey(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) {
        // 获取参数值
        Object[] args = joinPoint.getArgs();

        Signature signature = joinPoint.getSignature();

        String[] parameterNames = ((MethodSignature) signature).getParameterNames();
        // 获取token的下标
        int tokenIndex = ArrayUtils.indexOf(parameterNames, "token");
        // 获取token以及方法标记，生成redisKey和redisValue
        String token = String.valueOf(args[tokenIndex]);

        // 请求类名
        String className = joinPoint.getTarget().getClass().getName();
        className = className.substring(className.lastIndexOf("."));

        // 请求接口方法
        String pointName = signature.getName();

        String annotationValue = distributedLock.value();
        return StrUtil.isNotBlank(annotationValue)
                ? annotationValue
                : String.format(RedisConstant.ACCOUNT_USER_LOCK_KEY, token, className, pointName);
    }

}
